//    Copyright 2018 khorevaa
// 
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
// 
//        http://www.apache.org/licenses/LICENSE-2.0
// 
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// Модуль является переделкой публикации https://github.com/khorevaa/xml-parser. Спасибо автору

// Выполняет чтение данных xml в соответствие
//
// Параметры:
//   ПутьКФайлу - Строка - путь к файлу с данными xml
//   УпроститьЭлементы - Булево - нужно ли упрощать полученный объект
//
//  Возвращаемое значение:
//   Соответствие, Структура - результат чтения данных xml
//
Функция мПрочитать(Знач ПутьЧтения, УпроститьЭлементы = Истина) Экспорт
#Если ВебКлиент Тогда
	Возврат Неопределено;
#Иначе
		Если ТипЗнч(ПутьЧтения) = Тип("ЧтениеXML") Тогда
			Чтение = ПутьЧтения;
		ИначеЕсли ТипЗнч(ПутьЧтения) = Тип("Поток") Или ТипЗнч(ПутьЧтения) = Тип("ПотокВПамяти") Или ТипЗнч(ПутьЧтения)
			= Тип("ФайловыйПоток") Тогда
			Чтение=Новый ЧтениеXML;
			Чтение.ОткрытьПоток(ПутьЧтения);
		Иначе
			Чтение=Новый ЧтениеXML;
			Чтение.ОткрытьФайл(ПутьЧтения);
		КонецЕсли;

		Результат = ПрочитатьСекциюXML(Чтение, УпроститьЭлементы);

		Чтение.Закрыть();

		Возврат Результат;
#КонецЕсли

КонецФункции


// Выполняет сериализацию данных в файла
//
// Параметры:
//   ДанныеЗаписиXML - Соответствие, Массив, Структура, Число, Строка. Дата - данные для сериализации в XML
//   ПутьКФайлу - Строка - путь к файлу с данными xml
//   ЗаписатьОбъявлениеXML - Булево - признак добавления записи объявления XML
//
Процедура ЗаписатьВФайл(ДанныеЗаписиXML, Знач ПутьКФайлу, Знач ЗаписатьОбъявлениеXML = Ложь) Экспорт
#Если ТонкийКлиент Или ВебКлиент Тогда

#Иначе

		ЗаписьXML = Новый ЗаписьXML;
		ЗаписьXML.ОткрытьФайл(ПутьКФайлу);

		Если ЗаписатьОбъявлениеXML Тогда
			ЗаписьXML.ЗаписатьОбъявлениеXML();
		КонецЕсли;

		ЗаписатьСекциюXML(ЗаписьXML, ДанныеЗаписиXML);

		ЗаписьXML.Закрыть();
#КонецЕсли

КонецПроцедуры

#Если Не ТонкийКлиент И Не ВебКлиент Тогда
#Область Запись_данных_XML

// Выполняет сериализацию данных в XML
//
// Параметры:
//   ЗаписьXML - ЗаписьXML - подготовленная запись ЗаписьXML
//   ДанныеЗаписиXML - Соответствие, Массив, Структура, Число, Строка. Дата - данные для сериализации в XM
//
Процедура ЗаписатьСекциюXML(Знач ЗаписьXML, Знач ДанныеЗаписиXML)

	ЗаписатьЗначениеXML(ЗаписьXML, ДанныеЗаписиXML);

КонецПроцедуры
Процедура ЗаписатьЗначениеXML(Знач ЗаписьXML, Знач ДанныеЗаписиXML, Знач ИмяКорневогоУзла = "")

	НачатьЗаписьЭлемента = Не ПустаяСтрока(ИмяКорневогоУзла);

	Если НачатьЗаписьЭлемента Тогда
		ЗаписьXML.ЗаписатьНачалоЭлемента(XMLСтрока(ИмяКорневогоУзла));
	КонецЕсли;

	ТипДанныеЗаписиXML = ТипЗнч(ДанныеЗаписиXML);

	Если ТипДанныеЗаписиXML = Тип("Массив") Тогда
		ЗаписатьМассивВXML(ЗаписьXML, ДанныеЗаписиXML);
	ИначеЕсли ТипДанныеЗаписиXML = Тип("Соответствие") Или ТипДанныеЗаписиXML = Тип("Структура") Тогда
		ЗаписатьСоответствиеВXML(ЗаписьXML, ДанныеЗаписиXML);
	ИначеЕсли ЭтоПростойТип(ТипДанныеЗаписиXML) Тогда

		ЗаписатьТекст(ЗаписьXML, ДанныеЗаписиXML);

	КонецЕсли;

	Если НачатьЗаписьЭлемента Тогда
		ЗаписьXML.ЗаписатьКонецЭлемента();
	КонецЕсли;

КонецПроцедуры

Функция ЭтоПростойТип(Знач ТипДанных)

	Возврат ТипДанных = Тип("Число") Или ТипДанных = Тип("Строка") Или ТипДанных = Тип("Булево");

КонецФункции

Процедура ЗаписатьАтрибуты(ЗаписьXML, ДанныеАтрибутов)

	Если ДанныеАтрибутов.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;

	Для Каждого КлючЗначение Из ДанныеАтрибутов Цикл

		ЗаписьXML.ЗаписатьАтрибут(XMLСтрока(КлючЗначение.Ключ), XMLСтрока(КлючЗначение.Значение));

	КонецЦикла;

КонецПроцедуры

Процедура ЗаписатьСекцияCDATA(ЗаписьXML, ДанныеСекцияCDATA)

	Если Не ЗначениеЗаполнено(ДанныеСекцияCDATA) Тогда
		Возврат;
	КонецЕсли;

	ЗаписьXML.ЗаписатьСекциюCDATA(ДанныеСекцияCDATA);

КонецПроцедуры

Процедура ЗаписатьКомментарий(ЗаписьXML, Комментарий)

	Если Не ЗначениеЗаполнено(Комментарий) Тогда
		Возврат;
	КонецЕсли;

	ЗаписьXML.ЗаписатьКомментарий(XMLСтрока(Комментарий));

КонецПроцедуры

Процедура ЗаписатьТекст(ЗаписьXML, Текст)

	Если Не ЗначениеЗаполнено(Текст) Тогда
		Возврат;
	КонецЕсли;

	ЗаписьXML.ЗаписатьТекст(XMLСтрока(Текст));

КонецПроцедуры

Процедура ЗаписатьМассивВXML(ЗаписьXML, Знач ДанныеМассива)

	Для Каждого ЭлементМассива Из ДанныеМассива Цикл
		ЗаписатьЗначениеXML(ЗаписьXML, ЭлементМассива);
	КонецЦикла;

КонецПроцедуры

Процедура ЗаписатьСоответствиеВXML(ЗаписьXML, ДанныеСоответствия)

	Для Каждого КлючЗначение Из ДанныеСоответствия Цикл

		Если КлючЗначение.Ключ = "_Атрибуты" Тогда
			ЗаписатьАтрибуты(ЗаписьXML, КлючЗначение.Значение);
			Продолжить;
		КонецЕсли;

		Если КлючЗначение.Ключ = "_Комментарий" Тогда
			ЗаписатьКомментарий(ЗаписьXML, КлючЗначение.Значение);
			Продолжить;
		КонецЕсли;

		Если КлючЗначение.Ключ = "_CDATA" Тогда
			ЗаписатьСекцияCDATA(ЗаписьXML, КлючЗначение.Значение);
			Продолжить;
		КонецЕсли;

		Если КлючЗначение.Ключ = "_Значение" Тогда
			ЗаписатьТекст(ЗаписьXML, КлючЗначение.Значение);
			Продолжить;
		КонецЕсли;

		Если КлючЗначение.Ключ = "_Элементы" Тогда
			ЗаписатьЗначениеXML(ЗаписьXML, КлючЗначение.Значение);
			Продолжить;
		КонецЕсли;

		ЗаписатьЗначениеXML(ЗаписьXML, КлючЗначение.Значение, КлючЗначение.Ключ);

	КонецЦикла;

КонецПроцедуры

#КонецОбласти
#КонецЕсли

#Если Не ВебКлиент Тогда
#Область Чтение_данных_XML


// Выполняет чтение и десериализацию данных из XML
//
// Параметры:
//   ЧтениеXML - ЧтениеXML - <описание параметра>
//   ИмяКорневогоУзла - Строка - имя текущего узла, для вызова рекурсии
//
//  Возвращаемое значение:
//   Соответствие, Структура - результат чтения данных xml
//
Функция ПрочитатьСекциюXML(Знач ЧтениеXML, Знач УпроститьЭлементы, Знач ИмяКорневогоУзла = "")

	РезультатЧтения = Новый Структура;
	Атрибуты = Новый Соответствие;
	Элементы = Новый Соответствие;

	РезультатЧтения.Вставить("_Атрибуты", Атрибуты);
	РезультатЧтения.Вставить("_Элементы", Элементы);

//	Лог.Отладка("Начинаю чтение узла <%1> из XML.", ЧтениеXML.ЛокальноеИмя);

	ПрочитатьАтрибуты(ЧтениеXML, Атрибуты);

	Если Не ЧтениеXML.ТипУзла = ТипУзлаXML.КонецСущности Тогда
		ПрочитатьУзлы(ЧтениеXML, РезультатЧтения, УпроститьЭлементы, ИмяКорневогоУзла);
	КонецЕсли;

	УпроститьРезультатЧтения(РезультатЧтения, УпроститьЭлементы);

//	Лог.Отладка("Чтение узла <%1> закончено.", ЧтениеXML.ЛокальноеИмя);

	Возврат РезультатЧтения;

КонецФункции
Процедура УпроститьРезультатЧтения(РезультатЧтения, УпроститьЭлементы)

	Если Не УпроститьЭлементы Тогда
		Возврат;
	КонецЕсли;

	Если РезультатЧтения["_Атрибуты"].Количество() = 0 Тогда
		РезультатЧтения.Удалить("_Атрибуты");
	КонецЕсли;

	Если РезультатЧтения["_Элементы"].Количество() = 0 Тогда
		РезультатЧтения.Удалить("_Элементы");
	КонецЕсли;

	МожноУпростить = РезультатЧтения.Количество() = 1;

	Если МожноУпростить Тогда
		Если РезультатЧтения.Свойство("_Значение") Тогда
			РезультатЧтения = РезультатЧтения._Значение;
		ИначеЕсли РезультатЧтения.Свойство("_Элементы") Тогда
			РезультатЧтения = РезультатЧтения._Элементы;
		КонецЕсли;
	ИначеЕсли РезультатЧтения.Количество() = 0 Тогда
		РезультатЧтения = Неопределено;
	КонецЕсли;

КонецПроцедуры

Процедура ПрочитатьАтрибуты(Знач ЧтениеXML, Атрибуты)

	Если ЧтениеXML.КоличествоАтрибутов() = 0 Тогда
//		ИмяПоследнегоАтрибута = Неопределено;
		Возврат;
	КонецЕсли;

	Для ИндексАтрибута = 0 По ЧтениеXML.КоличествоАтрибутов() - 1 Цикл

		ИмяАтрибута = ЧтениеXML.ИмяАтрибута(ИндексАтрибута);
		ЗначениеАтрибута = ЧтениеXML.ЗначениеАтрибута(ИмяАтрибута);

//		Лог.Отладка("Прочитано значение <%1> атрибута <%2>", ЗначениеАтрибута, ИмяАтрибута);
		Атрибуты.Вставить(СокрЛП(ИмяАтрибута), СокрЛП(ЗначениеАтрибута));
//		ИмяПоследнегоАтрибута = ЧтениеXML.Имя;
	КонецЦикла;

КонецПроцедуры

Процедура ПрочитатьУзлы(Знач ЧтениеXML, КорневойУзел, УпроститьЭлементы, Знач ИмяКорневогоУзла)

	Пока ЧтениеXML.Прочитать() Цикл

//		Лог.Отладка("Тип узла <%1>", ЧтениеXML.ТипУзла);
//		Лог.Отладка("Имя узла <%1>", ЧтениеXML.ЛокальноеИмя);

		Если ЧтениеXML.ТипУзла = ТипУзлаXML.КонецЭлемента И ЧтениеXML.Имя = ИмяКорневогоУзла Тогда
			Прервать;
		КонецЕсли;

		Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда

			ИмяНовогоУзла = ЧтениеXML.Имя;
//			Лог.Отладка("Новый узел <%1>", ИмяНовогоУзла);
			СоответствиеУзла = ПрочитатьСекциюXML(ЧтениеXML, УпроститьЭлементы, ИмяНовогоУзла);
			ВставитьЭлементУзла(КорневойУзел, ИмяНовогоУзла, СоответствиеУзла);

		ИначеЕсли ЧтениеXML.ТипУзла = ТипУзлаXML.Текст Тогда

			ЗначениеСвойства = ЧтениеXML.Значение;
//			Лог.Отладка("Прочитано значение " + ЗначениеСвойства);
			КорневойУзел.Вставить("_Значение", ЗначениеСвойства);

		ИначеЕсли ЧтениеXML.ТипУзла = ТипУзлаXML.Комментарий Тогда
			ЗначениеСвойства = ЧтениеXML.Значение;
//			Лог.Отладка("Прочитан комментарий " + ЗначениеСвойства);
			КорневойУзел.Вставить("_Комментарий", ЗначениеСвойства);

		ИначеЕсли ЧтениеXML.ТипУзла = ТипУзлаXML.СекцияCDATA Тогда
			ЗначениеСвойства = ЧтениеXML.Значение;
//			Лог.Отладка("Прочитана СекцияCDATA " + ЗначениеСвойства);
			КорневойУзел.Вставить("_CDATA", ЗначениеСвойства);

		КонецЕсли;

	КонецЦикла;

КонецПроцедуры

Процедура ВставитьЭлементУзла(КорневойУзел, Знач ИмяНовогоУзла, СоответствиеУзла)

	ТипЭлементовУзла = ТипЗнч(КорневойУзел._Элементы);
	СоответствиеТекущегоЭлемента = Новый Соответствие;
	СоответствиеТекущегоЭлемента.Вставить(ИмяНовогоУзла, СоответствиеУзла);

	Если ТипЭлементовУзла = Тип("Массив") Тогда
		КорневойУзел._Элементы.Добавить(СоответствиеТекущегоЭлемента);
	ИначеЕсли ТипЭлементовУзла = Тип("Соответствие") Тогда

		УжеЕстьЗначение = Не КорневойУзел._Элементы[ИмяНовогоУзла] = Неопределено;

		Если УжеЕстьЗначение Тогда
			МассивЭлементов = Новый Массив;

			Для Каждого КлючЗначение Из КорневойУзел._Элементы Цикл
				ТекущийЭлемент = Новый Соответствие;
				ТекущийЭлемент.Вставить(КлючЗначение.Ключ, КлючЗначение.Значение);
				МассивЭлементов.Добавить(ТекущийЭлемент);
			КонецЦикла;
			МассивЭлементов.Добавить(СоответствиеТекущегоЭлемента);

			//@skip-warning
			КорневойУзел._Элементы  = МассивЭлементов;

		Иначе

			СоответствиеТекущегоЭлемента = Новый Соответствие;
			КорневойУзел._Элементы.Вставить(ИмяНовогоУзла, СоответствиеУзла);

		КонецЕсли;
	Иначе

		ВызватьИсключение СтрШаблон("Пришел не корректный тип значения <%1>", ТипЭлементовУзла);

	КонецЕсли;

КонецПроцедуры

#КонецОбласти
#КонецЕсли